package van.planifolia.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import van.planifolia.annotation.AopTarget;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Intellij IDEA<br>
 * 创建一个测试切面类，并且标注这个类为切面类
 *
 * @author Planifolia.Van
 * @version 1.0
 * @date 2022/11/11 16:59
 */
@Component
@Aspect
public class TestAspect {
    /**
     * 权限List
     */
    private List<String> permissions = new ArrayList<>();

    /**
     * 模拟当前用户的权限，只有查询和删除
     */
    public TestAspect() {
        permissions.add("search");
        permissions.add("remove");
    }

    /**
     * 这个空方法是必要的他是作为切入点的载体而他上面的@Pointcut注解是真正标记这个切入点位置的，
     * Pointcut中可以使用两种方式一种是execution表达式一种是自定义注解。我们再这就先使用execution表达式
     * 我们再这就先使用execution表达式，execution表达式也是有很严格的语法规范的具体如下
     * execution(* 包名字.类名.方法名 (一般使用..代表任意参数))我再下面写个例子读者可以意会一下
     * execution(void van.planifolia.service.*.* (..)) 再van.planifolia.service包中的所有类中的任意参数但是返回值为void的方法
     */
    @Pointcut("execution(void van.planifolia.service.impl.UserServiceImpl.* (..))")
    public void pointCarrier() {
    }

    /**
     * 创建第二个切入点，来切入Sp类
     */
    @Pointcut("execution(void van.planifolia.service.impl.UserServiceImplSp.* (..))")
    public void pointCarrierSp() {
    }

    /**
     * 创建第三个切入点，作为注解标记的测试,使用注解的切入点就非常简单了，只要在在@pointcut中添加
     * "@annotation(里面填写我们要作为标记的注解全限定名)"，然后这个切入点就会根据我们写入的注解标注的方法作为目标来进行
     * 切面织入
     */
    @Pointcut("@annotation(van.planifolia.annotation.AopTarget)")
    public void pointCarrierAnt() {
    }

    /**
     * 通知方法们也就是我们织入的业务代码，我们可以使用注解来指定他处理的时机
     *
     * @Before() 返回值后处理
     * @After() 方法前处理
     * @Around() 环绕处理需要在里面指定逻辑
     * @AfterThrowing() 异常后处理
     * 比如下面这个方法我采用的就是前置处理
     */
    @Before("pointCarrier()")
    public void beforeAdvice() {
        System.out.println("前置权限校验");
    }

    /**
     * 我们在这就使用Around来模拟环绕通知
     *
     * @param joinPoint 连接点也就是被增强的那个方法
     */
    @Around("pointCarrierSp()")
    public void aroundAdvice(ProceedingJoinPoint joinPoint) {
        System.out.println("方法前通知");
        try {
            // 这一句就代表方法执行，写在他上面的就是方法前通知，写在他后面的就是返回后通知，写在finally中的就是最终通知
            // 写在catch中的就是异常后通知
            joinPoint.proceed();
            System.out.println("方法返回后通知");
        } catch (Throwable throwable) {
            System.out.println("异常通知");
            throwable.printStackTrace();
        } finally {
            System.out.println("最终通知");
        }
    }

    /**
     * 使用注解标注的权限检验方法
     * @param joinPoint 连接点 也就是我们被增强的方法
     * @return 需要返回的信息
     * @throws Throwable 抛出的异常
     */
    @Around("pointCarrierAnt()")
    public Object throwingAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        // 首先我们要获取当前拦截方法的Method对象，可以通过我们传入的参数 连接点(被增强方法封装的)对象
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 然后我们获取方法上注解标记的权限需求，也就是我们自定义方法上的属性
        AopTarget annotation = method.getAnnotation(AopTarget.class);
        String value = annotation.value();
        // 然后我们要去判断当前这个被标记的方法上的这个权限在List中到底有没有这个属性值，若没有则直接返回权限不足的提示，
        // 因为我们这个是被增强到Controller方法之前上的所以这里返回会直接显示到页面上
        if (!permissions.contains(value)) {
            return "Insufficient permissions！";
        }
        // 存在的话我们就正常返回让他正常走,就是调用这个方法
        return joinPoint.proceed();
    }
}
